﻿using System;
using System.Collections.Generic;
using System.Linq;

namespace ProyectoDeTesis.Controladores
{
    public class ResultadoAlgoritmoController
    {
        private SimulacionController simulacionController = new SimulacionController();
        private Configuracion configuracion = new Configuracion();
        private VueloController vueloController = new VueloController();
        private ConfiguracionController configuracionController = new ConfiguracionController();
        private PuertaEmbarqueController puertaEmbarqueController = new PuertaEmbarqueController();
        private List<PuertaEmbarque> listaPuertaEmbarques = new List<PuertaEmbarque>();        

        public List<Vuelo> obtenerVuelos(DateTime dia) 
        {
            List<Vuelo> listaVuelosXDia = vueloController.obtenerVuelosXDia(dia);

            listaVuelosXDia = ordenarVuelos(listaVuelosXDia);

            return listaVuelosXDia;
        }

        private List<Vuelo> ordenarVuelos(List<Vuelo> list)
        {
            List<Vuelo> listaVuelo = new List<Vuelo>();

            list.Sort(delegate(Vuelo x, Vuelo y) 
            {
                return compararVuelos(x, y);
            });

            listaVuelo = list;

            return listaVuelo;
        }

        private int compararVuelos(Vuelo x, Vuelo y)
        {
            int tiempoX = obtenerTiempoSegundos(x) - obtenerTiempoVuelo(x);
            int tiempoY = obtenerTiempoSegundos(x) - obtenerTiempoVuelo(y);

            if (tiempoX == tiempoY)
            {
                return 0;
            }
            else if (tiempoX > tiempoY)
            {
                return 1;
            }
            else
            {
                return -1;
            }
        }

        private int obtenerTiempoAbordaje(Vuelo v)
        {
            return Convert.ToInt32(v.num_pasajeros) * configuracion.tiempo_abordaje_x_pasajero;
        }

        private int obtenerTiempoCarga(Vuelo v)
        { 
            return  Convert.ToInt32(v.carga) * configuracion.tiempo_carga_x_equipaje;
        }

        private int obtenerTiempoVuelo(Vuelo v)
        {
            int tiempoVuelo = 0;
            int tiempoAbordaje = obtenerTiempoAbordaje(v);
            int tiempoCarga = obtenerTiempoCarga(v);

            if (tiempoCarga > tiempoAbordaje)
            {
                tiempoVuelo = tiempoCarga;
            }
            else 
            {
                tiempoVuelo =  tiempoAbordaje;
            }

            return tiempoVuelo + configuracion.tiempo_extra;
        }

        private int obtenerTiempoSegundos(Vuelo v)
        {
            int hora = Convert.ToInt32(v.hora_salida.Split(':')[0]);
            int minuto = Convert.ToInt32(v.hora_salida.Split(':')[1]);

            return hora * 60 * 60 + minuto * 60;
        }

        public List<ResultadoAlgoritmo> asignarPuertaEmbarques(DateTime dia, Configuracion configuracion) {
            List<ResultadoAlgoritmo> listaResultadoAlgoritmo = new List<ResultadoAlgoritmo>();
            PuertaEmbarque pe;
            ResultadoAlgoritmo resultadoAlgoritmo;
            List<Vuelo> listaVuelos = obtenerVuelos(dia);
            this.configuracion = configuracion;
            listaPuertaEmbarques = puertaEmbarqueController.obtenerPuertasEmbarque();
            List<ProyectoDeTesis.PuertaEmbarque> listaPuertaEmbarquesDisponibles = listaPuertaEmbarques.Where(c => c.disponible == "Si").ToList();
            int numPuertaEmbarquesDisponibles = listaPuertaEmbarquesDisponibles.Count;
            //Condición de parada de fin de dia
            TimeSpan time = new TimeSpan(0, 0, 0);
            TimeSpan finDia = new TimeSpan(1, 0, 0, 0);
            int numVuelosAntesDia = 0;

            while (time < finDia)
            {
                //obtiene los indices de los vuelos que necesitan iniciar y terminar el abordaje en el tiempo
                List<List<int>> inicios_fines = evaluarTiempo(listaVuelos, dia.Add(time));
                //obtiene indices de los vuelos que necesitan inicios de abordaje
                List<int> inicios = inicios_fines.ElementAt(0);
                //obtiene indices de los vuelos que necesitan fines de abordaje
                List<int> fines = inicios_fines.ElementAt(1);

                if (inicios.Count > 0)
                {
                    List<int> vuelosEncolados = new List<int>();
                    for (int j = 0; j < inicios.Count; j++) 
                    {
                        //buscar puerta de embarque disponible
                        pe = existePuertaEmbarqueDisponible();

                        if (pe != null)
                        {
                            resultadoAlgoritmo = new ResultadoAlgoritmo();
                            resultadoAlgoritmo.PuertaEmbarque = pe;
                            pe.disponible = "No";
                            resultadoAlgoritmo.Vuelo = listaVuelos.ElementAt(inicios.ElementAt(j));
                            resultadoAlgoritmo.num_vuelo = resultadoAlgoritmo.Vuelo.num_vuelo;
                            resultadoAlgoritmo.num_puerta = resultadoAlgoritmo.PuertaEmbarque.num_puerta;
                            resultadoAlgoritmo.inicio_abordaje = dia.Add(time).AddSeconds(obtenerTiempoPistaPrincipal(pe) * -1);
                            listaResultadoAlgoritmo.Add(resultadoAlgoritmo);
                        }
                        else {
                            vuelosEncolados.Add(inicios.ElementAt(j));                            
                        }
                    }
                    if (vuelosEncolados.Count > 0)
                    {
                        listaVuelos = reasignarFinAbordaje(listaVuelos, vuelosEncolados, time);
                        listaVuelos = ordenarVuelosPorPrioridad(listaVuelos, vuelosEncolados);
                    }
                }

                if (fines.Count > 0)
                {
                    for (int k = 0; k < fines.Count; k++)
                    {

                        Vuelo vuelo = listaVuelos.ElementAt(fines.ElementAt(k));
                        if (obtenerTiempoSegundos(vuelo) - obtenerTiempoVuelo(vuelo) > 0)
                        {
                            resultadoAlgoritmo = buscarResultadoAlgoritmoVuelo(listaResultadoAlgoritmo, vuelo);
                            resultadoAlgoritmo.fin_abordaje = dia.Add(time);
                            habilitarPuertaEmbarque(resultadoAlgoritmo.PuertaEmbarque);
                        }
                        else 
                        {
                            pe = listaPuertaEmbarquesDisponibles.ElementAt(numVuelosAntesDia % numPuertaEmbarquesDisponibles);
                            resultadoAlgoritmo = new ResultadoAlgoritmo();
                            resultadoAlgoritmo.PuertaEmbarque = pe;
                            resultadoAlgoritmo.Vuelo = vuelo;
                            resultadoAlgoritmo.num_puerta = pe.num_puerta;
                            resultadoAlgoritmo.num_vuelo = vuelo.num_vuelo;
                            resultadoAlgoritmo.fin_abordaje = dia.Add(time);
                            resultadoAlgoritmo.inicio_abordaje = Convert.ToDateTime(resultadoAlgoritmo.fin_abordaje).AddSeconds(agregarTiempoVuelo(resultadoAlgoritmo) * -1);
                            listaResultadoAlgoritmo.Add(resultadoAlgoritmo);
                            numVuelosAntesDia++;
                        }
                    }
                }                

                time = time.Add(TimeSpan.FromSeconds(1));
            }

            foreach (ResultadoAlgoritmo ra in listaResultadoAlgoritmo)
            {
                if (ra.fin_abordaje == null)
                { 
                    ra.fin_abordaje = Convert.ToDateTime(ra.inicio_abordaje).AddSeconds(agregarTiempoVuelo(ra));
                }
            }

            return listaResultadoAlgoritmo;
        }

        private int agregarTiempoVuelo(ResultadoAlgoritmo ra)
        {
            Vuelo vuelo = ra.Vuelo;
            PuertaEmbarque pe = ra.PuertaEmbarque;
            int tiempoVuelo = obtenerTiempoVuelo(vuelo);

            tiempoVuelo += obtenerTiempoPistaPrincipal(pe);

            return tiempoVuelo;
        }

        private int obtenerTiempoPistaPrincipal(PuertaEmbarque pe)
        {
            return Convert.ToInt32(pe.distancia_pista_principal / configuracion.velocidad_max_avion) * 3600;
        }

        private ResultadoAlgoritmo buscarResultadoAlgoritmoVuelo(List<ResultadoAlgoritmo> listaResultadoAlgoritmo, Vuelo vuelo)
        {
            ResultadoAlgoritmo resultado = new ResultadoAlgoritmo();
            foreach (ResultadoAlgoritmo ra in listaResultadoAlgoritmo)
            {
                if (ra.vuelo_id == vuelo.id)
                {
                    return ra;
                }
            }

            return resultado;
        }

        private List<Vuelo> reasignarFinAbordaje(List<Vuelo> listaVuelos, List<int> vuelosEncolados, TimeSpan time)
        {
            for (int i = 0; i < vuelosEncolados.Count; i++) 
            {
                listaVuelos.ElementAt(vuelosEncolados.ElementAt(i)).hora_salida = time.ToString(@"hh\:mm\:ss");
            }

            return listaVuelos;
        }

        private List<Vuelo> ordenarVuelosPorPrioridad(List<Vuelo> listaVuelos, List<int> vuelosEncolados)
        {
            List<Vuelo> vuelosEncoladosOrdenados = new List<Vuelo>();
            List<Vuelo> vuelosEmergencia = new List<Vuelo>();
            List<Vuelo> vuelosNormales = new List<Vuelo>();

            for (int i = 0; i < vuelosEncolados.Count; i++) 
            {
                Vuelo v = listaVuelos.ElementAt(vuelosEncolados.ElementAt(i));
                if (v.tipo_vuelo == "Emergencia")
                {
                    vuelosEmergencia.Add(listaVuelos.ElementAt(vuelosEncolados.ElementAt(i)));
                }
                else if (v.tipo_vuelo == "Normal")
                {
                    vuelosNormales.Add(listaVuelos.ElementAt(vuelosEncolados.ElementAt(i)));
                }
            }

            vuelosEmergencia = ordenarVuelos(vuelosEmergencia);
            vuelosNormales = ordenarVuelos(vuelosNormales);

            vuelosEncoladosOrdenados.AddRange(vuelosEmergencia);
            vuelosEncoladosOrdenados.AddRange(vuelosNormales);

            for (int j = 0; j < vuelosEncoladosOrdenados.Count; j++) 
            {
                listaVuelos.RemoveAt(vuelosEncolados.ElementAt(j));

                listaVuelos.Insert(vuelosEncolados.ElementAt(j), vuelosEncoladosOrdenados.ElementAt(j));
            }

            return listaVuelos;
        }

        private void habilitarPuertaEmbarque(PuertaEmbarque pe)
        {
            foreach (PuertaEmbarque puerta_embarque in listaPuertaEmbarques)
            {
                if (puerta_embarque.id == pe.id)
                {
                    puerta_embarque.disponible = "Si";
                    break;
                }
            }
        }

        private List<List<int>> evaluarTiempo(List<Vuelo> listaVuelos, DateTime fechahora)
        {
            List<List<int>> inicios_fines = new List<List<int>>();

            List<int> inicios = new List<int>();

            List<int> fines = new List<int>();

            for (int i = 0; i < listaVuelos.Count; i++) 
            {
                Vuelo v = listaVuelos.ElementAt(i);                  
                DateTime fin_abordaje = v.fecha_salida.Add(TimeSpan.Parse(v.hora_salida));
                DateTime inicio_abordaje = fin_abordaje.AddSeconds(obtenerTiempoVuelo(v) * -1);

                if (inicio_abordaje == fechahora)
                {
                    inicios.Add(i);
                }
                if (fin_abordaje == fechahora)
                {
                    fines.Add(i);
                }

            }

            inicios_fines.Add(inicios);
            inicios_fines.Add(fines);

            return inicios_fines;
        }

        private PuertaEmbarque existePuertaEmbarqueDisponible()
        {
            foreach (PuertaEmbarque pe in listaPuertaEmbarques)
            {
                if (pe.disponible.CompareTo("Si") == 0)
                {
                    return pe;
                }
            }

            return null;
        }

        public List<ResultadoAlgoritmo> obtenerResultadosAlgoritmo(DateTime fecha)
        {
            var db = new Bd_tesisDataContext();
            Simulacion simulacion = simulacionController.obtenerSimulacion(fecha);
            List<ResultadoAlgoritmo> listaResultadoAlgoritmo = new List<ResultadoAlgoritmo>();

            try
            {
                var query = from ra in db.ResultadoAlgoritmo
                            where ra.simulacion_id == simulacion.id
                            select ra;

                listaResultadoAlgoritmo = query.ToList();
            }
            catch (Exception e)
            {
                Console.WriteLine(e.ToString());
            }

            return listaResultadoAlgoritmo;
        }
    }

}
